// Base64.java - A utility class for MIME encoding/decoding.
//
// Copyright (C) 1999-2002  Smart Software Consulting
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// Smart Software Consulting
// 1688 Silverwood Court
// Danville, CA  94526-3079
// USA
//
// http://www.smartsc.com
//

package com.smartsc.util;

public class Base64
{
	private static final char[] encoding =
	{
		'A','B','C','D','E','F','G','H','I','J','K','L','M',
		'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
		'a','b','c','d','e','f','g','h','i','j','k','l','m',
		'n','o','p','q','r','s','t','u','v','w','x','y','z',
		'0','1','2','3','4','5','6','7','8','9','+','/'
	};

	private static byte encode( byte decoded)
	{
		return (byte)encoding[decoded];
	}

	private static byte decode( byte encoded)
	{
		if( encoded >= 'a') return (byte)(encoded - 'a' + 26);
		if( encoded >= 'A') return (byte)(encoded - 'A');
		if( encoded >= '0') return (byte)(encoded - '0' + 52);
		if( encoded == '/') return 63;
		if( encoded == '+') return 62;
		throw new IllegalArgumentException();
	}

	public static String encode( String decoded)
	{
		return new String( encode( decoded.getBytes()));
	}

	public static byte[] encode( byte[] decoded)
	{
		int encodedLength = 4 * ((decoded.length + 2) / 3);
		byte[] encoded = new byte[encodedLength];
		byte[] sextets = new byte[4];

		// Loop through decoded array three bytes at a time
		int encodedIdx = 0;
		for( int i = 0; i < decoded.length; i += 3)
		{
			byte b0 = decoded[i];
			byte b1 = (i + 1 < decoded.length) ? decoded[i+1] : 0;
			byte b2 = (i + 2 < decoded.length) ? decoded[i+2] : 0;

			// Transform three bytes into four sextets
			sextets[0] = (byte)(b0 >>> 2);
			sextets[1] = (byte)(((b0 & 0x3) << 4) | (b1 >>> 4));
			sextets[2] = (byte)(((b1 & 0xf) << 2) | b2 >>> 6);
			sextets[3] = (byte)(b2 & 0x3f);

			// Encode sextets into buffer
			encoded[encodedIdx++] = encode( sextets[0]);
			encoded[encodedIdx++] = encode( sextets[1]);
			encoded[encodedIdx++] = encode( sextets[2]);
			encoded[encodedIdx++] = encode( sextets[3]);
		}

		// Trim extra encoded bytes
		if( decoded.length % 3 == 1)
		{
			encoded[encodedLength-2] = (byte)'=';
			encoded[encodedLength-1] = (byte)'=';
		}
		else if( decoded.length % 3 == 2)
		{
			encoded[encodedLength-1] = (byte)'=';
		}

		return encoded;
	}

	public static String decode( String encoded)
	{
		return new String( decode( encoded.getBytes()));
	}

	public static byte[] decode( byte[] encoded)
	{
		int decodedLength = 3 * ((encoded.length + 3) / 4);
		if( encoded[encoded.length - 1] == (byte)'=')
		{
			--decodedLength;
			if( encoded[encoded.length - 2] == (byte)'=')
			{
				--decodedLength;
			}
		}

		byte[] decoded = new byte[decodedLength];
		byte[] sextets = new byte[4];

		// Loop through encoded bytes four at a time
		int decodedIdx = 0;
		for( int i = 0; i < encoded.length; i += 4)
		{
			byte b0 = encoded[i];
			byte b1 = (i + 1 < encoded.length) ? encoded[i+1] : 0;
			byte b2 = (i + 2 < encoded.length) ? encoded[i+2] : 0;
			byte b3 = (i + 3 < encoded.length) ? encoded[i+3] : 0;
			if( b2 == (byte)'=') b2 = 0;
			if( b3 == (byte)'=') b3 = 0;

			// Decode bytes into sextets
			sextets[0] = decode( b0);
			sextets[1] = decode( b1);

			// Transform four sextets into three bytes
			decoded[decodedIdx++] = (byte)(
				(sextets[0] << 2) | (sextets[1] >>> 4) );

			if( decodedIdx < decodedLength)
			{
				sextets[2] = decode( b2);
				decoded[decodedIdx++] = (byte)(
					((sextets[1] & 0xf) << 4) | (sextets[2] >>> 2) );

				if( decodedIdx < decodedLength)
				{
					sextets[3] = decode( b3);
					decoded[decodedIdx++] = (byte)(
						((sextets[2] & 0x3) << 6) | sextets[3] );
				}
			}
		}

		return decoded;
	}

	/*
	public static void main( String[] args)
	{
		String plain = "ABCDEFGHIJKLMNOPQRSTUZWXYZ";
		if( args.length > 0) plain = args[0];

		String encoded = encode( plain);
		String decoded = decode( encoded);

		System.out.println( "plaintext= '" + plain + "'");
		System.out.println( "encoded  = '" + encoded + "'");
		System.out.println( "decoded  = '" + decoded + "'");
	}
	//*/
}
